using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.ServiceProcess;
using System.Web.Mail;
using System.Text;
using NLog;
using System.Globalization;

namespace MailQueueProcessingService
{
	public class MailService : System.ServiceProcess.ServiceBase
	{
        private static Logger logger = LogManager.GetCurrentClassLogger();
        private static bool isProcessingQueue = false;

		private System.Timers.Timer timerProcessingService;
		/// <summary> 
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

		public MailService()
		{
			// This call is required by the Windows.Forms Component Designer.
			InitializeComponent();

			// TODO: Add any initialization after the InitComponent call
		}

		// The main entry point for the process
		static void Main()
		{
            try
            {
                System.ServiceProcess.ServiceBase[] ServicesToRun;

                // More than one user Service may run within the same process. To add
                // another service to this process, change the following line to
                // create a second service object. For example,
                //
                //   ServicesToRun = New System.ServiceProcess.ServiceBase[] {new Service1(), new MySecondUserService()};
                //
                ServicesToRun = new System.ServiceProcess.ServiceBase[] { new MailService() };

                System.ServiceProcess.ServiceBase.Run(ServicesToRun);
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
            }
		}

		/// <summary> 
		/// Required method for Designer support - do not modify 
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{
            try
            {
                Settings.Load(System.Configuration.ConfigurationManager.ConnectionStrings[0].ConnectionString);
                this.timerProcessingService = new System.Timers.Timer();
                ((System.ComponentModel.ISupportInitialize)(this.timerProcessingService)).BeginInit();
                // 
                // timerProcessingService
                // 
                this.timerProcessingService.Enabled = true;
                this.timerProcessingService.Interval = Settings.SendInterval;
                this.timerProcessingService.Elapsed += new System.Timers.ElapsedEventHandler(this.timerProcessingService_Elapsed);
                // 
                // MailService
                // 
                this.ServiceName = "MailQueue";
                ((System.ComponentModel.ISupportInitialize)(this.timerProcessingService)).EndInit();
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
            }
		}

		/// <summary>
		/// Clean up any resources being used.
		/// </summary>
		protected override void Dispose( bool disposing )
		{
			if( disposing )
			{
				if (components != null) 
				{
					components.Dispose();
				}
			}
			base.Dispose( disposing );
		}

		/// <summary>
		/// Set things in motion so your service can do its work.
		/// </summary>
		protected override void OnStart(string[] args)
		{
			// TODO: Add code here to start your service.
			EventLog.WriteEntry("Started");
		}
 
		/// <summary>
		/// Stop this service.
		/// </summary>
		protected override void OnStop()
		{
			// TODO: Add code here to perform any tear-down necessary to stop your service.
			EventLog.WriteEntry("Stopped");
		}

		private void timerProcessingService_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
		{
            if (!isProcessingQueue)
            {
                isProcessingQueue = true;

                foreach (System.Configuration.ConnectionStringSettings s in System.Configuration.ConfigurationManager.ConnectionStrings)
                {
                    Settings.Load(s.ConnectionString);

                    this.timerProcessingService.Interval = Settings.SendInterval;

                    if (!Settings.Pause)
                    {
                        StringBuilder mailQueueIDs = new StringBuilder();
                        StringBuilder errorMailQueueIDs = new StringBuilder();
                        StringBuilder warningMailQueueIDs = new StringBuilder();
                        
                        Guid mailQueueID = Guid.Empty;
                        int retryCount = 0;

                        try
                        {
                            //EventLog.WriteEntry("Elapsed");
                            //Get all the mails from the database

                            DataSet mailDataSet = GetMails(s.ConnectionString);
                            DataTable mailTable = mailDataSet.Tables[0];

                            foreach (DataRow row in mailTable.Rows)
                            {
                                string mailSender = (string)row["Sender"];
                                string fromName = (string)row["FromName"];
                                string from = (string)row["FromMailAddress"];
                                string toList = (string)row["ToMailAddress"];
                                string ccList = (string)row["CCList"];
                                string bccList = (string)row["BccList"];
                                string subject = (string)row["Subject"];
                                int importance = (int)row["Priority"];
                                string body = (string)row["Body"];
                                int mailFormat = (int)row["mailFormat"];
                                retryCount = (int)row["retryCount"];
                                //string attachments = (string)row["Attachments"];
                                mailQueueID = (Guid)row["MailQueueID"];
                                if (SendMail(mailSender, fromName, from, toList, ccList, bccList, subject, importance, body, mailQueueID, mailFormat, s.ConnectionString))
                                {
                                    if (mailQueueIDs.Length > 0)
                                        mailQueueIDs.Append(",");
                                    mailQueueIDs.Append("'" + mailQueueID.ToString() + "'");
                                }
                                else
                                {
                                    if (retryCount < Settings.Retries)
                                    {
                                        if (warningMailQueueIDs.Length > 0)
                                            warningMailQueueIDs.Append(",");

                                        warningMailQueueIDs.Append("'" + mailQueueID.ToString() + "'");
                                    }
                                    else
                                    {

                                        if (errorMailQueueIDs.Length > 0)
                                            errorMailQueueIDs.Append(",");

                                        errorMailQueueIDs.Append("'" + mailQueueID.ToString() + "'");
                                    }
                                }

                                mailQueueID = Guid.Empty;
                            }
                        }
                        catch (Exception ex)
                        {
                            logger.Error(ex.Message);

                            if (!mailQueueID.Equals(Guid.Empty))
                                EnterErrorMessageInDatabase(mailQueueID, ex.Message + "\n------------\n" + ex, s.ConnectionString);

                            if (retryCount < Settings.Retries)
                            {
                                if (warningMailQueueIDs.Length > 0)
                                    warningMailQueueIDs.Append(",");

                                warningMailQueueIDs.Append("'" + mailQueueID.ToString() + "'");
                            }
                            else
                            {

                                if (errorMailQueueIDs.Length > 0)
                                    errorMailQueueIDs.Append(",");

                                errorMailQueueIDs.Append("'" + mailQueueID.ToString() + "'");
                            }
                        }

                        //Update the status for all the Sent mails
                        UpdateMailStatus(mailQueueIDs.ToString(), 2, s.ConnectionString);
                        UpdateMailStatus(warningMailQueueIDs.ToString(), 4, s.ConnectionString);
                        UpdateMailStatus(errorMailQueueIDs.ToString(), 8, s.ConnectionString);
                    }
                }
                isProcessingQueue = false;
            }
		}

        private static DataSet GetMails(string connString)
		{
            try
            {
                Guid g = Guid.NewGuid();
                BeginMailProcess(g, connString);

                DataSet mailDataSet = new DataSet();
                mailDataSet.Locale = CultureInfo.CurrentCulture;
                using (SqlConnection sqlConn = new SqlConnection(connString))
                {
                    SqlDataAdapter adapter = new SqlDataAdapter("usp_GetMailMessage '{" + g.ToString() + "}'", sqlConn);
                    adapter.Fill(mailDataSet);
                    sqlConn.Close();
                }

                return mailDataSet;
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
                return null;
            }
		}

        private static void BeginMailProcess(Guid g, string connString)
        {
            try
            {
                SqlConnection sqlConn = new SqlConnection(connString);
                sqlConn.Open();
                SqlCommand cmd = sqlConn.CreateCommand();
                cmd.CommandText = "usp_BeginMailProcess";
                cmd.CommandType = CommandType.StoredProcedure;

                SqlParameter par = new SqlParameter("ProcessId", g);
                cmd.Parameters.Add(par);

                int i = cmd.ExecuteNonQuery();
                sqlConn.Close();
                //if (i>0)
                //    logger.Info(String.Format(CultureInfo.CurrentCulture, "Start processing {0} mails...", i.ToString(CultureInfo.CurrentCulture)));
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
            }
        }

        private static void PurgeOldQueueItems(string connString)
        {
            try
            {
                SqlConnection sqlConn = new SqlConnection(connString);
                sqlConn.Open();
                SqlCommand cmd = sqlConn.CreateCommand();
                cmd.CommandText = "usp_PurgeOldQueueItems";
                cmd.CommandType = CommandType.StoredProcedure;

                int i = cmd.ExecuteNonQuery();
                sqlConn.Close();
                if (i>0)
                    logger.Info(String.Format(CultureInfo.CurrentCulture, "Purged {0} mails...", i.ToString(CultureInfo.CurrentCulture)));
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
            }
        }

        private static void UpdateMailStatus(string mailQueueIDs, int StatusCode, string connString)
		{
            try
            {
                SqlConnection sqlConn = new SqlConnection(connString);
                sqlConn.Open();
                SqlCommand command = null;
                
                command = new SqlCommand("usp_UpdateMailMessage", sqlConn);
                
                command.CommandType = CommandType.StoredProcedure;

                //Set the MailQueueIDs Parameter
                SqlParameter paramMailQueueIDs = new SqlParameter("@MailQueueIDs", SqlDbType.VarChar, 7000);
                paramMailQueueIDs.Direction = ParameterDirection.Input;
                paramMailQueueIDs.Value = mailQueueIDs;
                command.Parameters.Add(paramMailQueueIDs);

                SqlParameter paramStatusCode = new SqlParameter("@status", SqlDbType.Int);
                paramStatusCode.Direction = ParameterDirection.Input;
                paramStatusCode.Value = StatusCode;
                command.Parameters.Add(paramStatusCode);

                //Execute the stored procedure
                command.ExecuteNonQuery();
                sqlConn.Close();
            }
            catch (Exception ex)
            {
                logger.Error(ex.Message);
            }
		}

        private static bool SendMail(string sender, string fromName, string from, string toList, string ccList, string bccList, string subject, int importance, string body, Guid mailQueueID, int mailFormat, string connString)
		{
            bool returnValue = true;

            //Get information from the app.config
            string smtpServer = "";
            string smtpUser = String.Empty;
            string smtpPass = String.Empty;
            int smtpPort = 25;
            bool smtpSSL = false;
            string smtpOnBehalf = String.Empty;
            string smtpDummy = String.Empty;

            smtpServer = Settings.SmtpServer;

            try
            {
                smtpUser = Settings.SmtpUser;
                smtpPass = Settings.SmtpPass;
            }
            catch
            {
                //No user credentials in app.config
            }


            try
            {
                smtpPort = Settings.SmtpPort;
            }
            catch
            {
                //No port in app.config
            }

            try
            {
                smtpSSL = Settings.SmtpSsl;
            }
            catch
            {
                //No SLL in app.config
            }

            try
            {
                smtpOnBehalf = Settings.SmtpOnBehalf;
            }
            catch
            {
                //Do not send on behalf of others
            }

            //This dummy address is used to filter out mails that should not be sent. If the reciepient is equal to this, then it will just be ignored.
            try
            {
                smtpDummy = Settings.SmtpDummy;
            }
            catch
            {
                //No SLL in app.config
            }

            //The email address smtpDummy is an email address that should never be sent, and not result in an error
            //So if the receipient equals this email address we catch the message here, and pretend we sent the message and just abort.
            if (!String.IsNullOrEmpty(smtpDummy) && smtpDummy.ToLower(CultureInfo.CurrentCulture).Equals(toList.ToLower(CultureInfo.CurrentCulture)))
            {
                returnValue = true;
                return returnValue;
            }

            //Set a priority
            System.Net.Mail.MailPriority p = (System.Net.Mail.MailPriority)importance;
            
            //Create the message object
            System.Net.Mail.MailMessage mail = new System.Net.Mail.MailMessage();

            //If we want to send on behalf of someone else, then create 
            if (!String.IsNullOrEmpty(smtpOnBehalf) || !String.IsNullOrEmpty(sender))
            {
                //smtpOnBehalf overrides the sender
                smtpOnBehalf = !String.IsNullOrEmpty(smtpOnBehalf) ? smtpOnBehalf : sender;

                mail.Sender = new System.Net.Mail.MailAddress(String.Format(CultureInfo.CurrentCulture, "{0} <{1}>", fromName, smtpOnBehalf));
                mail.From = new System.Net.Mail.MailAddress(String.Format(CultureInfo.CurrentCulture, "{0} <{1}>", fromName, from));
                mail.ReplyTo = new System.Net.Mail.MailAddress(String.Format(CultureInfo.CurrentCulture, "{0} <{1}>", fromName, from));
            }
            else
            {
                mail.From = new System.Net.Mail.MailAddress(String.Format(CultureInfo.CurrentCulture, "{0} <{1}>", fromName, from));
            }
            
            //Add receipients
            mail.To.Add(toList);
            
            //Add CCs
            if (ccList.Trim().Length > 0)
                mail.CC.Add(ccList);
            
            //Add BCCs
            if (bccList.Trim().Length > 0)
                mail.Bcc.Add(bccList);
            
            //Set subject
            mail.Subject = subject;

            //Set body
            mail.Body = body;

            //Set the format to either HTML or TEXT
            if (mailFormat==0)
                mail.IsBodyHtml = true;
            else
                mail.IsBodyHtml = false;

            //Set the priority
            mail.Priority = p;

            //Create the SMTP Client
            try
            {
                System.Net.Mail.SmtpClient client = new System.Net.Mail.SmtpClient(smtpServer);

                //Do we need to authenticate ourselves on the SMTP server?
                if (!String.IsNullOrEmpty(smtpUser))
                {
                    System.Net.NetworkCredential basicAuthenticationInfo = new System.Net.NetworkCredential(smtpUser, smtpPass);

                    client.UseDefaultCredentials = false;
                    client.Credentials = basicAuthenticationInfo;
                }

                //Set the SMTP port
                client.Port = smtpPort==0 ? 25 : smtpPort;                

                //Do we need to encrypt the SMTP connection
                client.EnableSsl = smtpSSL;

                //Send email 
                client.Send(mail);

                //Log action
                logger.Info(String.Format(CultureInfo.CurrentCulture, "{0}\t{1}\t{2}\tMail sent", mailQueueID, toList, subject));

            }
            catch (Exception ex)
            {
                //Log the error
                string iEx = String.Empty;
                logger.Error(ex.Message);
                if (ex.InnerException != null)
                    iEx = ex.InnerException.Message;
                logger.Error(String.Format(CultureInfo.CurrentCulture, "{0}\t{1}\t{2}\t{3}\t{4}", mailQueueID, toList, subject, ex.Message, iEx));
                returnValue = false;

                //Also log this in the database
                EnterErrorMessageInDatabase(mailQueueID, ex.Message + "\n------------\n" + iEx, connString);
            }

            //Return success
            return returnValue;
        }

        private static void GetMailAttachments(Guid MailQueueId, string connString)
        {
            SqlConnection conn = new SqlConnection(connString);
            SqlCommand cmd = new SqlCommand("usp_DownloadAttachment", conn);
            cmd.CommandType = System.Data.CommandType.StoredProcedure;
        }

        private static void EnterErrorMessageInDatabase(Guid mailQueueID, string errorMessage, string connString)
        {
            try
            {
                SqlConnection sqlConn = new SqlConnection(connString);
                sqlConn.Open();
                SqlCommand command = sqlConn.CreateCommand();

                command.CommandText = "INSERT INTO Errors (MailQueueId, ErrorMessage) VALUES (@mid, @error)";

                command.CommandType = CommandType.Text;

                //Set the MailQueueIDs Parameter
                SqlParameter paramMailQueueID = new SqlParameter("@mid", mailQueueID);
                paramMailQueueID.Direction = ParameterDirection.Input;
                command.Parameters.Add(paramMailQueueID);

                SqlParameter paramError = new SqlParameter("@error", errorMessage);
                paramError.Direction = ParameterDirection.Input;
                command.Parameters.Add(paramError);

                //Execute the stored procedure
                command.ExecuteNonQuery();
                sqlConn.Close();
            }
            catch (Exception ex)
            {
                string iEx = String.Empty;
                logger.Error(ex.Message);
                if (ex.InnerException != null)
                    iEx = ex.InnerException.Message;
                logger.Error(String.Format(CultureInfo.CurrentCulture, "{0}\t{1}", mailQueueID, "Error not logged"));
            }
        }
    }
}
